#nullable enable
// ReSharper disable once CheckNamespace
namespace Fluent;

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using Fluent.Extensibility;
using Fluent.Internal;

/// <summary>
/// Represents adorner for KeyTips.
/// KeyTipAdorners is chained to produce one from another.
/// Detaching root adorner couses detaching all adorners in the chain
/// </summary>
public class KeyTipAdorner : Adorner
{
    #region Events

    /// <summary>
    /// This event is occured when adorner is
    /// detached and is not able to be attached again
    /// </summary>
    public event EventHandler<KeyTipPressedResult>? Terminated;

    #endregion

    #region Fields

    // KeyTips that have been
    // found on this element
    private readonly List<KeyTipInformation> keyTipInformations = new();

    private readonly FrameworkElement oneOfAssociatedElements;

    // Parent adorner
    private readonly KeyTipAdorner? parentAdorner;
    private KeyTipAdorner? childAdorner;

    private readonly FrameworkElement keyTipElementContainer;

    // Is this adorner attached to the adorned element?
    private bool attached;
    private bool isAttaching;

    // Designate that this adorner is terminated
    private bool terminated;

    private AdornerLayer? adornerLayer;

    #endregion

    #region Properties

    /// <summary>
    /// Determines whether at least one on the adorners in the chain is alive
    /// </summary>
    public bool IsAdornerChainAlive
    {
        get { return this.isAttaching || this.attached || this.childAdorner?.IsAdornerChainAlive == true; }
    }

    /// <summary>
    /// Returns whether any key tips are visibile.
    /// </summary>
    public bool AreAnyKeyTipsVisible
    {
        get { return this.keyTipInformations.Any(x => x.IsVisible) || this.childAdorner?.AreAnyKeyTipsVisible == true; }
    }

    /// <summary>
    /// Gets the currently active <see cref="KeyTipAdorner"/> by following eventually present child adorners.
    /// </summary>
    public KeyTipAdorner ActiveKeyTipAdorner
    {
        get
        {
            return this.childAdorner?.IsAdornerChainAlive == true
                ? this.childAdorner.ActiveKeyTipAdorner
                : this;
        }
    }

    /// <summary>
    /// Gets a copied list of the currently available <see cref="KeyTipInformation"/>.
    /// </summary>
    public IReadOnlyList<KeyTipInformation> KeyTipInformations
    {
        get
        {
            return this.keyTipInformations.ToList();
        }
    }

    #endregion

    #region Intialization

    /// <summary>
    /// Construcotor
    /// </summary>
    /// <param name="adornedElement">Element to adorn.</param>
    /// <param name="parentAdorner">Parent adorner or null.</param>
    /// <param name="keyTipElementContainer">The element which is container for elements.</param>
    public KeyTipAdorner(FrameworkElement adornedElement, FrameworkElement keyTipElementContainer, KeyTipAdorner? parentAdorner)
        : base(adornedElement)
    {
        this.parentAdorner = parentAdorner;

        this.keyTipElementContainer = keyTipElementContainer;

        // Try to find supported elements
        this.FindKeyTips(this.keyTipElementContainer, false);
        this.oneOfAssociatedElements = this.keyTipInformations.Count != 0
                ? this.keyTipInformations[0].AssociatedElement
                : adornedElement // Maybe here is bug, coz we need keytipped item here...
            ;
    }

    // Find key tips on the given element
    private void FindKeyTips(FrameworkElement container, bool hide)
    {
        var children = GetVisibleChildren(container);

        foreach (var child in children)
        {
            var groupBox = child as RibbonGroupBox;

            var keys = KeyTip.GetKeys(child);

            if (keys is not null
                || child is IKeyTipInformationProvider)
            {
                if (groupBox is null)
                {
                    this.GenerateAndAddRegularKeyTipInformations(keys, child, hide);

                    // Do not search deeper in the tree
                    continue;
                }

                if (keys is not null)
                {
                    this.GenerateAndAddGroupBoxKeyTipInformation(hide, keys, child, groupBox);
                }
            }

            var innerHide = hide || groupBox?.State == RibbonGroupBoxState.Collapsed;
            this.FindKeyTips(child, innerHide);
        }
    }

    private void GenerateAndAddGroupBoxKeyTipInformation(bool hide, string keys, FrameworkElement child, RibbonGroupBox groupBox)
    {
        var keyTipInformation = new KeyTipInformation(keys, child, hide || groupBox.State != RibbonGroupBoxState.Collapsed);

        // Add to list & visual children collections
        this.AddKeyTipInformationElement(keyTipInformation);

        this.LogDebug("Found KeyTipped RibbonGroupBox \"{0}\" with keys \"{1}\".", keyTipInformation.AssociatedElement, keyTipInformation.Keys);
    }

    private void GenerateAndAddRegularKeyTipInformations(string? keys, FrameworkElement child, bool hide)
    {
        IEnumerable<KeyTipInformation>? informations = null;

        if (child is IKeyTipInformationProvider keyTipInformationProvider)
        {
            informations = keyTipInformationProvider.GetKeyTipInformations(hide);
        }
        else if (keys is not null)
        {
            informations = new[] { new KeyTipInformation(keys, child, hide) };
        }

        if (informations is not null)
        {
            foreach (var keyTipInformation in informations)
            {
                // Add to list & visual children collections
                this.AddKeyTipInformationElement(keyTipInformation);

                this.LogDebug("Found KeyTipped element \"{0}\" with keys \"{1}\".", keyTipInformation.AssociatedElement, keyTipInformation.Keys);
            }
        }
    }

    private void AddKeyTipInformationElement(KeyTipInformation keyTipInformation)
    {
        this.keyTipInformations.Add(keyTipInformation);
        this.AddVisualChild(keyTipInformation.KeyTip);
    }

    private static IList<FrameworkElement> GetVisibleChildren(FrameworkElement element)
    {
        var logicalChildren = LogicalTreeHelper.GetChildren(element)
            .OfType<FrameworkElement>();

        var children = logicalChildren;

        // Always using the visual tree is very expensive, so we only search through it when we got specific control types.
        // Using the visual tree here, in addition to the logical, partially fixes #244.
        if (element is ContentPresenter
            || element is ContentControl)
        {
            children = children
                .Concat(UIHelper.GetVisualChildren(element))
                .OfType<FrameworkElement>();
        }
        else if (element is ItemsControl itemsControl)
        {
            children = children.Concat(UIHelper.GetAllItemContainers<FrameworkElement>(itemsControl));
        }

        // Don't show key tips for the selected content too early
        if (element is RibbonTabControl ribbonTabControl
            && ribbonTabControl.SelectedContent is FrameworkElement selectedContent)
        {
            children = children.Except(new[] { selectedContent });
        }

        return children
            .Where(x => x.Visibility == Visibility.Visible)
            .Distinct()
            .ToList();
    }

    #endregion

    #region Attach & Detach

    /// <summary>
    /// Attaches this adorner to the adorned element
    /// </summary>
    public void Attach()
    {
        if (this.attached)
        {
            return;
        }

        this.isAttaching = true;

        this.oneOfAssociatedElements.UpdateLayout();

        this.LogDebug("Attach begin {0}", this.Visibility);

        if (this.oneOfAssociatedElements.IsLoaded == false)
        {
            // Delay attaching
            this.LogDebug("Delaying attach");
            this.oneOfAssociatedElements.Loaded += this.OnDelayAttach;
            return;
        }

        this.adornerLayer = GetAdornerLayer(this.oneOfAssociatedElements);

        if (this.adornerLayer is null)
        {
            this.LogDebug("No adorner layer found");
            this.isAttaching = false;
            return;
        }

        this.FilterKeyTips(string.Empty);

        // Show this adorner
        this.adornerLayer.Add(this);

        this.isAttaching = false;
        this.attached = true;

        this.LogDebug("Attach end");
    }

    private void OnDelayAttach(object sender, EventArgs args)
    {
        this.LogDebug("Delay attach (control loaded)");
        this.oneOfAssociatedElements.Loaded -= this.OnDelayAttach;
        this.Attach();
    }

    /// <summary>
    /// Detaches this adorner from the adorned element
    /// </summary>
    public void Detach()
    {
        this.childAdorner?.Detach();

        if (!this.attached)
        {
            return;
        }

        this.LogDebug("Detach Begin");

        // Maybe adorner awaiting attaching, cancel it
        this.oneOfAssociatedElements.Loaded -= this.OnDelayAttach;

        // Show this adorner
        this.adornerLayer?.Remove(this);

        this.attached = false;

        this.LogDebug("Detach End");
    }

    #endregion

    #region Termination

    /// <summary>
    /// Terminate whole key tip's adorner chain
    /// </summary>
    public void Terminate(KeyTipPressedResult keyTipPressedResult)
    {
        if (this.terminated)
        {
            return;
        }

        this.terminated = true;

        this.Detach();

        this.parentAdorner?.Terminate(keyTipPressedResult);

        this.childAdorner?.Terminate(keyTipPressedResult);

        this.Terminated?.Invoke(this, keyTipPressedResult);

        this.LogDebug("Termination");
    }

    #endregion

    #region Static Methods

    private static AdornerLayer? GetAdornerLayer(UIElement? element)
    {
        var current = element;

        while (true)
        {
            if (current is null)
            {
                return null;
            }

            var parent = (UIElement?)VisualTreeHelper.GetParent(current)
                         ?? (UIElement?)LogicalTreeHelper.GetParent(current);

            current = parent;

            if (current is AdornerDecorator)
            {
                return AdornerLayer.GetAdornerLayer((UIElement)VisualTreeHelper.GetChild(current, 0));
            }
        }
    }

    private static UIElement GetTopLevelElement(UIElement element)
    {
        while (true)
        {
            var current = VisualTreeHelper.GetParent(element) as UIElement;

            if (current is null)
            {
                return element;
            }

            element = current;
        }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Back to the previous adorner.
    /// </summary>
    public void Back()
    {
        this.LogTrace("Invoking back.");

        var control = this.keyTipElementContainer as IKeyTipedControl;
        control?.OnKeyTipBack();

        if (this.parentAdorner is not null)
        {
            this.LogDebug("Back");
            this.Detach();
            this.parentAdorner.Attach();
        }
        else
        {
            this.Terminate(KeyTipPressedResult.Empty);
        }
    }

    /// <summary>
    /// Forwards to the elements with the given keys
    /// </summary>
    /// <param name="keys">Keys</param>
    /// <param name="click">If true the element will be clicked</param>
    /// <returns>If the element will be found the function will return true</returns>
    public bool Forward(string keys, bool click)
    {
        this.LogTrace("Trying to forward keys \"{0}\"...", keys);

        var keyTipInformation = this.TryGetKeyTipInformation(keys);
        if (keyTipInformation is null)
        {
            this.LogTrace("Found no element for keys \"{0}\".", keys);
            return false;
        }

        this.Forward(keys, keyTipInformation.AssociatedElement, click);
        return true;
    }

    /// <summary>
    /// Forwards to the elements with the given keys to a given element.
    /// </summary>
    /// <param name="keys">Keys</param>
    /// <param name="element">The element to forward to.</param>
    /// <param name="click">If true the element will be clicked</param>
    public void Forward(string keys, FrameworkElement element, bool click)
    {
        this.LogTrace("Forwarding keys \"{0}\" to element \"{1}\".", keys, GetControlLogText(element));

        this.Detach();
        var keyTipPressedResult = KeyTipPressedResult.Empty;

        if (click)
        {
            this.LogTrace("Invoking click.");

            var control = element as IKeyTipedControl;
            keyTipPressedResult = control?.OnKeyTipPressed() ?? KeyTipPressedResult.Empty;
        }

        var children = GetVisibleChildren(element);

        if (children.Count == 0)
        {
            this.Terminate(keyTipPressedResult);
            return;
        }

        // Panels aren't good elements to adorn. For example, trying to display KeyTips on MenuItems in SplitButton fails if using a panel.
        var validChild = children.FirstOrDefault(x => x is Panel == false) ?? children[0];
        this.childAdorner = ReferenceEquals(GetTopLevelElement(validChild), GetTopLevelElement(element)) == false
            ? new KeyTipAdorner(validChild, element, this)
            : new KeyTipAdorner(element, element, this);

        // Stop if no further KeyTips can be displayed.
        if (this.childAdorner.keyTipInformations.Any() == false)
        {
            this.Terminate(keyTipPressedResult);
            return;
        }

        this.childAdorner.Attach();
    }

    /// <summary>
    /// Gets <see cref="KeyTipInformation"/> by keys.
    /// </summary>
    /// <param name="keys">The keys to look for.</param>
    /// <returns>The <see cref="KeyTipInformation"/> associated with <paramref name="keys"/>.</returns>
    private KeyTipInformation? TryGetKeyTipInformation(string keys)
    {
        return this.keyTipInformations.FirstOrDefault(x => x.IsEnabled && x.Visibility == Visibility.Visible && keys.Equals(x.Keys, StringComparison.OrdinalIgnoreCase));
    }

    /// <summary>
    /// Determines if an of the keytips contained in this adorner start with <paramref name="keys"/>
    /// </summary>
    /// <returns><c>true</c> if any keytip start with <paramref name="keys"/>. Otherwise <c>false</c>.</returns>
    public bool ContainsKeyTipStartingWith(string keys)
    {
        foreach (var keyTipInformation in this.keyTipInformations.Where(x => x.IsEnabled))
        {
            var content = keyTipInformation.Keys;

            if (content.StartsWith(keys, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
        }

        return false;
    }

    // Hide / unhide keytips relative matching to entered keys
    internal void FilterKeyTips(string keys)
    {
        this.LogDebug("FilterKeyTips with \"{0}\"", keys);

        // Reset visibility if filter is empty
        if (string.IsNullOrEmpty(keys))
        {
            foreach (var keyTipInformation in this.keyTipInformations)
            {
                keyTipInformation.Visibility = keyTipInformation.DefaultVisibility;
            }
        }

        // Backup current visibility of key tips
        foreach (var keyTipInformation in this.keyTipInformations)
        {
            keyTipInformation.BackupVisibility = keyTipInformation.Visibility;
        }

        // Hide / unhide keytips relative matching to entered keys
        foreach (var keyTipInformation in this.keyTipInformations)
        {
            var content = keyTipInformation.Keys;

            if (string.IsNullOrEmpty(keys))
            {
                keyTipInformation.Visibility = keyTipInformation.BackupVisibility;
            }
            else
            {
                keyTipInformation.Visibility = content.StartsWith(keys, StringComparison.OrdinalIgnoreCase)
                    ? keyTipInformation.BackupVisibility
                    : Visibility.Collapsed;
            }
        }

        this.LogDebug("Filtered key tips: {0}", this.keyTipInformations.Count(x => x.Visibility == Visibility.Visible));
    }

    #endregion

    #region Layout & Visual Children

    /// <inheritdoc />
    protected override Size ArrangeOverride(Size finalSize)
    {
        this.LogDebug("ArrangeOverride");

        foreach (var keyTipInformation in this.keyTipInformations)
        {
            keyTipInformation.KeyTip.Arrange(new Rect(keyTipInformation.Position, keyTipInformation.KeyTip.DesiredSize));
        }

        return finalSize;
    }

    /// <inheritdoc />
    protected override Size MeasureOverride(Size constraint)
    {
        this.LogDebug("MeasureOverride");

        foreach (var keyTipInformation in this.keyTipInformations)
        {
            keyTipInformation.KeyTip.Measure(SizeConstants.Infinite);
        }

        this.UpdateKeyTipPositions();

        var result = new Size(0, 0);
        foreach (var keyTipInformation in this.keyTipInformations)
        {
            var cornerX = keyTipInformation.KeyTip.DesiredSize.Width + keyTipInformation.Position.X;
            var cornerY = keyTipInformation.KeyTip.DesiredSize.Height + keyTipInformation.Position.Y;

            if (cornerX > result.Width)
            {
                result.Width = cornerX;
            }

            if (cornerY > result.Height)
            {
                result.Height = cornerY;
            }
        }

        return result;
    }

    private void UpdateKeyTipPositions()
    {
        this.LogDebug("UpdateKeyTipPositions");

        if (this.keyTipInformations.Count == 0)
        {
            return;
        }

        double[]? rows = null;
        var groupBox = this.oneOfAssociatedElements as RibbonGroupBox ?? UIHelper.GetParent<RibbonGroupBox>(this.oneOfAssociatedElements);
        var panel = groupBox?.GetPanel();

        if (panel is not null
            && groupBox is not null)
        {
            var layoutRoot = groupBox.GetLayoutRoot();

            if (layoutRoot is not null)
            {
                var height = layoutRoot.DesiredSize.Height;
                rows = new[]
                {
                    layoutRoot.TranslatePoint(new Point(0, 0), this.AdornedElement).Y,
                    layoutRoot.TranslatePoint(new Point(0, panel.DesiredSize.Height / 2.0), this.AdornedElement).Y,
                    layoutRoot.TranslatePoint(new Point(0, panel.DesiredSize.Height), this.AdornedElement).Y,
                    layoutRoot.TranslatePoint(new Point(0, height + 1), this.AdornedElement).Y
                };
            }
        }

        foreach (var keyTipInformation in this.keyTipInformations)
        {
            // Skip invisible keytips
            if (keyTipInformation.Visibility != Visibility.Visible)
            {
                continue;
            }

            // Update KeyTip Visibility
            var visualTargetIsVisible = keyTipInformation.VisualTarget.IsVisible;
            var visualTargetInVisualTree = VisualTreeHelper.GetParent(keyTipInformation.VisualTarget) is not null;
            keyTipInformation.Visibility = visualTargetIsVisible && visualTargetInVisualTree ? Visibility.Visible : Visibility.Collapsed;

            keyTipInformation.KeyTip.Margin = KeyTip.GetMargin(keyTipInformation.AssociatedElement);

            if (IsWithinQuickAccessToolbar(keyTipInformation.AssociatedElement))
            {
                var x = (keyTipInformation.VisualTarget.DesiredSize.Width / 2.0) - (keyTipInformation.KeyTip.DesiredSize.Width / 2.0);
                var y = keyTipInformation.VisualTarget.DesiredSize.Height - (keyTipInformation.KeyTip.DesiredSize.Height / 2.0);

                if (KeyTip.GetAutoPlacement(keyTipInformation.AssociatedElement) == false)
                {
                    switch (KeyTip.GetHorizontalAlignment(keyTipInformation.AssociatedElement))
                    {
                        case HorizontalAlignment.Left:
                            x = 0;
                            break;
                        case HorizontalAlignment.Right:
                            x = keyTipInformation.VisualTarget.DesiredSize.Width - keyTipInformation.KeyTip.DesiredSize.Width;
                            break;
                    }
                }

                keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(new Point(x, y), this.AdornedElement);
            }
            else if (keyTipInformation.AssociatedElement.Name == "PART_DialogLauncherButton")
            {
                // Dialog Launcher Button Exclusive Placement
                var keyTipSize = keyTipInformation.KeyTip.DesiredSize;
                var elementSize = keyTipInformation.VisualTarget.RenderSize;
                if (rows is null)
                {
                    continue;
                }

                keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(new Point(
                    (elementSize.Width / 2.0) - (keyTipSize.Width / 2.0),
                    0), this.AdornedElement);
                keyTipInformation.Position = new Point(keyTipInformation.Position.X, rows[3]);
            }
            else if (KeyTip.GetAutoPlacement(keyTipInformation.AssociatedElement) == false)
            {
                var keyTipSize = keyTipInformation.KeyTip.DesiredSize;

                var elementSize = keyTipInformation.VisualTarget.RenderSize;

                double x = 0, y = 0;

                switch (KeyTip.GetHorizontalAlignment(keyTipInformation.AssociatedElement))
                {
                    case HorizontalAlignment.Left:
                        break;
                    case HorizontalAlignment.Right:
                        x = elementSize.Width - keyTipSize.Width;
                        break;
                    case HorizontalAlignment.Center:
                    case HorizontalAlignment.Stretch:
                        x = (elementSize.Width / 2.0) - (keyTipSize.Width / 2.0);
                        break;
                }

                switch (KeyTip.GetVerticalAlignment(keyTipInformation.AssociatedElement))
                {
                    case VerticalAlignment.Top:
                        break;
                    case VerticalAlignment.Bottom:
                        y = elementSize.Height - keyTipSize.Height;
                        break;
                    case VerticalAlignment.Center:
                    case VerticalAlignment.Stretch:
                        y = (elementSize.Height / 2.0) - (keyTipSize.Height / 2.0);
                        break;
                }

                keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(new Point(x, y), this.AdornedElement);
            }
            else if (keyTipInformation.AssociatedElement is InRibbonGallery gallery
                     && gallery.IsCollapsed == false)
            {
                // InRibbonGallery Exclusive Placement
                var keyTipSize = keyTipInformation.KeyTip.DesiredSize;
                var elementSize = keyTipInformation.VisualTarget.RenderSize;
                if (rows is null)
                {
                    continue;
                }

                keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(new Point(
                    elementSize.Width - (keyTipSize.Width / 2.0),
                    0), this.AdornedElement);
                keyTipInformation.Position = new Point(keyTipInformation.Position.X, rows[2] - (keyTipSize.Height / 2));
            }
            else if (keyTipInformation.AssociatedElement is RibbonTabItem || keyTipInformation.AssociatedElement is Backstage)
            {
                // Ribbon Tab Item Exclusive Placement
                var keyTipSize = keyTipInformation.KeyTip.DesiredSize;
                var elementSize = keyTipInformation.VisualTarget.RenderSize;
                keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(new Point(
                    (elementSize.Width / 2.0) - (keyTipSize.Width / 2.0),
                    elementSize.Height - (keyTipSize.Height / 2.0)), this.AdornedElement);
            }
            else if (keyTipInformation.AssociatedElement is MenuItem)
            {
                // MenuItem Exclusive Placement
                var elementSize = keyTipInformation.VisualTarget.DesiredSize;
                keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(
                    new Point(
                        (elementSize.Height / 3.0) + 2,
                        (elementSize.Height / 4.0) + 2), this.AdornedElement);
            }
            else if (keyTipInformation.AssociatedElement.Parent is BackstageTabControl)
            {
                // Backstage Items Exclusive Placement
                var keyTipSize = keyTipInformation.KeyTip.DesiredSize;
                var elementSize = keyTipInformation.VisualTarget.DesiredSize;
                var parent = (UIElement)keyTipInformation.VisualTarget.Parent;
                var positionInParent = keyTipInformation.VisualTarget.TranslatePoint(default, parent);
                keyTipInformation.Position = parent.TranslatePoint(
                    new Point(
                        5,
                        positionInParent.Y + ((elementSize.Height / 2.0) - keyTipSize.Height)), this.AdornedElement);
            }
            else
            {
                if (RibbonProperties.GetSize(keyTipInformation.AssociatedElement) != RibbonControlSize.Large
                    || IsTextBoxShapedControl(keyTipInformation.AssociatedElement))
                {
                    var x = keyTipInformation.KeyTip.DesiredSize.Width / 2.0;
                    var y = keyTipInformation.KeyTip.DesiredSize.Height / 2.0;
                    var point = new Point(x, y);
                    var translatedPoint = keyTipInformation.VisualTarget.TranslatePoint(point, this.AdornedElement);

                    // Snapping to rows if it present
                    SnapToRowsIfPresent(rows, keyTipInformation, translatedPoint);

                    keyTipInformation.Position = translatedPoint;
                }
                else
                {
                    var x = (keyTipInformation.VisualTarget.DesiredSize.Width / 2.0) - (keyTipInformation.KeyTip.DesiredSize.Width / 2.0);
                    var y = keyTipInformation.VisualTarget.DesiredSize.Height - 8;
                    var point = new Point(x, y);
                    var translatedPoint = keyTipInformation.VisualTarget.TranslatePoint(point, this.AdornedElement);

                    // Snapping to rows if it present
                    SnapToRowsIfPresent(rows, keyTipInformation, translatedPoint);

                    keyTipInformation.Position = translatedPoint;
                }
            }
        }
    }

    private static bool IsTextBoxShapedControl(FrameworkElement element)
    {
        return element is Spinner || element is System.Windows.Controls.ComboBox || element is System.Windows.Controls.TextBox || element is System.Windows.Controls.CheckBox;
    }

    // Determines whether the element is children to RibbonToolBar
    private static bool IsWithinRibbonToolbarInTwoLine(DependencyObject element)
    {
        var ribbonToolBar = UIHelper.GetParent<RibbonToolBar>(element);

        var definition = ribbonToolBar?.GetCurrentLayoutDefinition();
        if (definition is null)
        {
            return false;
        }

        if (definition.RowCount == 2
            || definition.Rows.Count == 2)
        {
            return true;
        }

        return false;
    }

    // Determines whether the element is children to quick access toolbar
    private static bool IsWithinQuickAccessToolbar(DependencyObject element)
    {
        return UIHelper.GetParent<QuickAccessToolBar>(element) is not null;
    }

    private static void SnapToRowsIfPresent(double[]? rows, KeyTipInformation keyTipInformation, Point translatedPoint)
    {
        if (rows is null)
        {
            return;
        }

        var withinRibbonToolbar = IsWithinRibbonToolbarInTwoLine(keyTipInformation.VisualTarget);

        var index = 0;
        var mindistance = Math.Abs(rows[0] - translatedPoint.Y);
        for (var j = 1; j < rows.Length; j++)
        {
            if (withinRibbonToolbar && j == 1)
            {
                continue;
            }

            var distance = Math.Abs(rows[j] - translatedPoint.Y);
            if (distance < mindistance)
            {
                mindistance = distance;
                index = j;
            }
        }

        translatedPoint.Y = rows[index] - (keyTipInformation.KeyTip.DesiredSize.Height / 2.0);
    }

    /// <inheritdoc />
    protected override int VisualChildrenCount => this.keyTipInformations.Count;

    /// <inheritdoc />
    protected override Visual GetVisualChild(int index)
    {
        return this.keyTipInformations[index].KeyTip;
    }

    #endregion

    #region Logging

    [Conditional("DEBUG")]
    private void LogDebug(string format, params object[] args)
    {
        var message = this.GetMessageLog(format, args);

        Debug.WriteLine(message, "KeyTipAdorner");
    }

    [Conditional("TRACE")]
    private void LogTrace(string format, params object[] args)
    {
        var message = this.GetMessageLog(format, args);

        Trace.WriteLine(message, "KeyTipAdorner");
    }

    private string GetMessageLog(string format, object[] args)
    {
        var name = GetControlLogText(this.AdornedElement);

        var message = $"[{name}] {string.Format(format, args)}";
        return message;
    }

    private static string GetControlLogText(UIElement control)
    {
        var name = control.GetType().Name;

        if (control is IHeaderedControl headeredControl)
        {
            name += $" ({headeredControl.Header})";
        }

        return name;
    }

    #endregion
}